use derive_more::{Add, AddAssign, Constructor, From, Into, Mul, MulAssign, Sub, SubAssign};
use serde::{Deserialize, Serialize};
use std::ops::{Add, Index, IndexMut};

#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
pub enum Axis {
    Horizontal,
    Vertical,
}

impl Axis {
    pub const EACH: [Axis; 2] = [Axis::Horizontal, Axis::Vertical];

    pub fn face(self, positive: bool) -> Dir {
        match (self, positive) {
            (Axis::Horizontal, true) => Dir::Right,
            (Axis::Horizontal, false) => Dir::Left,
            (Axis::Vertical, true) => Dir::Down,
            (Axis::Vertical, false) => Dir::Up,
        }
    }

    pub fn rotate(self) -> Self {
        match self {
            Axis::Horizontal => Axis::Vertical,
            Axis::Vertical => Axis::Horizontal,
        }
    }
}

#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
pub enum Dir {
    Down,
    Left,
    Right,
    Up,
}

impl Dir {
    pub const EACH: [Dir; 4] = [Dir::Down, Dir::Left, Dir::Right, Dir::Up];

    pub fn axis(self) -> Axis {
        match self {
            Dir::Down | Dir::Up => Axis::Vertical,
            Dir::Left | Dir::Right => Axis::Horizontal,
        }
    }

    pub fn positive(self) -> bool {
        matches!(self, Dir::Down | Dir::Right)
    }

    pub fn reverse(self) -> Self {
        self.axis().face(!self.positive())
    }
}

#[derive(
    Clone,
    Copy,
    PartialEq,
    Eq,
    Hash,
    Default,
    Debug,
    Add,
    Sub,
    Mul,
    AddAssign,
    SubAssign,
    MulAssign,
    Constructor,
    Into,
    From,
    Serialize,
    Deserialize,
)]
pub struct Coord {
    pub x: usize,
    pub y: usize,
}

impl Index<Axis> for Coord {
    type Output = usize;
    fn index(&self, index: Axis) -> &Self::Output {
        match index {
            Axis::Horizontal => &self.x,
            Axis::Vertical => &self.y,
        }
    }
}
impl IndexMut<Axis> for Coord {
    fn index_mut(&mut self, index: Axis) -> &mut Self::Output {
        match index {
            Axis::Horizontal => &mut self.x,
            Axis::Vertical => &mut self.y,
        }
    }
}

/// 溢出则返回空
impl Add<Dir> for Coord {
    type Output = Option<Self>;
    fn add(mut self, rhs: Dir) -> Self::Output {
        let comp = &mut self[rhs.axis()];
        let diff = if rhs.positive() { 1 } else { -1 };
        *comp = comp.checked_add_signed(diff)?;
        Some(self)
    }
}
